package com.forcht.client;

import com.forcht.client.handle.ResponseHandler;
import com.forcht.client.view.ChatView;
import com.forcht.client.view.FriendsListView;
import com.forcht.client.view.LoginView;
import com.forcht.common.domain.*;
import com.forcht.common.enumeration.MessageType;
import com.forcht.common.util.ProtostuffUtil;
import com.forcht.common.util.SpringContextUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Created by forcht on 2018/4/11.
 * 客户端
 */
public class Client {

    private static final int DEFAULT_BUFFER_SIZE = 1024;
    private static final String HOSTNAME = "127.0.0.1";
    private static final int DEFAULT_PORT = 80;
    private SocketChannel socketChannel;
    private Selector selector;
    private ByteBuffer buffer;
    private volatile boolean connection;
    private ListenerThread listenerThread;
    //客户端面板
    private LoginView loginView;
    private FriendsListView friendsListView;
    private Map<String, ChatView> chatViewMap;
    //用户
    private User user;

    /**
     * 初始化客户端
     */
    private void init() {
        try {
            //创建客户端通道
            socketChannel = SocketChannel.open(new InetSocketAddress(HOSTNAME, DEFAULT_PORT));
            socketChannel.configureBlocking(false);
            //创建注册器监听通道事件
            selector = Selector.open();
            socketChannel.register(selector, SelectionKey.OP_READ);
            //申请客户端的缓存
            buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
            connection = true;
            chatViewMap = new HashMap<>();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 用户登录
     * @param username
     * @param password
     */
    public void login(String username, char[] password) {
        try {
            //发送登录信息给服务器
            Message message = Message.builder().header(MessageHeader.builder()
                    .sender(username)
                    .messageType(MessageType.LOGIN)
                    .timestamp(new Date()).build())
                    .body(String.copyValueOf(password).getBytes())
                    .build();
            socketChannel.write(ByteBuffer.wrap(ProtostuffUtil.serialize(message)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 用户下线
     */
    public void logout(){
        try {
            //发送下线信息给服务器
            Message message = Message.builder().header(MessageHeader.builder()
                    .sender(user.getUsername())
                    .messageType(MessageType.LOGOUT)
                    .timestamp(new Date()).build())
                    .body("下线了".getBytes())
                    .build();
            socketChannel.write(ByteBuffer.wrap(ProtostuffUtil.serialize(message)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 启动客户端
     */
    public void launch() {
        init();
        listenerThread = new ListenerThread();
        listenerThread.start();
    }

    /**
     * 关闭客户端
     */
    public void close() {
        listenerThread.interrupt();
    }


    /**
     * 监听是否可以读取数据
     */
    private class ListenerThread extends Thread {
        /**
         * 通过中断来停止客户端
         */
        @Override
        public void interrupt() {
            try {
                connection = false;
                socketChannel.socket().close();
                socketChannel.close();
                selector.wakeup();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                super.interrupt();
            }
        }

        @Override
        public void run() {
            while (connection) {
                try {
                    //当注册在selector上的通道有事件到达时，方法返回，否则select()方法阻塞
                    selector.select();
                    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                    while (keys.hasNext()) {
                        //获得监听的事件
                        SelectionKey key = keys.next();
                        //删除该事件
                        keys.remove();
                        //读取数据
                        if (key.isReadable()) {
                            ByteArrayOutputStream out = new ByteArrayOutputStream();
                            int size = 0;
                            while ((size = socketChannel.read(buffer)) > 0) {
                                buffer.flip();
                                out.write(buffer.array(), 0, size);
                                buffer.clear();
                            }
                            byte[] bytes = out.toByteArray();
                            Response response = ProtostuffUtil.deserialize(bytes, Response.class);
                            //获取一个消息处理器
                            ResponseHeader header = response.getHeader();
                            ResponseHandler responseHandler = SpringContextUtil.getBean(
                                    header.getResponseType().toString().toLowerCase() + "Handler",
                                    header.getResponseCode().toString().toLowerCase());
                            //处理服务器的响应
                            responseHandler.handle(response, Client.this);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public LoginView getLoginView() {
        return loginView;
    }

    public void setLoginView(LoginView loginView) {
        this.loginView = loginView;
    }

    public FriendsListView getFriendsListView() {
        return friendsListView;
    }

    public void setFriendsListView(FriendsListView friendsListView) {
        this.friendsListView = friendsListView;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public void addChatView(String username, ChatView chatView) {
        chatViewMap.put(username, chatView);
    }

    public void removeChatView(String username){
        chatViewMap.remove(username);
    }

    public ChatView getChatView(String username) {
        return chatViewMap.get(username);
    }

    public SocketChannel getSocketChannel() {
        return socketChannel;
    }

    public void setSocketChannel(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }
}
